from num2words import num2words
from babel.dates import format_date
from datetime import timedelta
from dateutil.relativedelta import relativedelta

from odoo import fields, models, api, _
from odoo.exceptions import UserError, ValidationError


class AccountIncoterms(models.Model):
    _inherit = 'account.incoterms'

    def _compute_display_name(self):
        res = super()._compute_display_name()
        for rec in self:
            if self.env.context.get('force_code_display'):
                rec.display_name = rec.code
        return res


from num2words import num2words

# 中文数字映射
digits_cn = "零壹贰叁肆伍陆柒捌玖"
units_cn = ["", "拾", "佰", "仟"]
big_units_cn = ["", "万", "亿"]


def number_to_chinese(amount: float) -> str:
    """
    将金额转换为中文大写（人民币格式）
    """
    amount = round(amount, 2)  # 保留两位
    integer_part = int(amount)
    decimal_part = round((amount - integer_part) * 100)

    # 处理整数部分
    def integer_to_cn(num):
        if num == 0:
            return "零"
        result = ""
        unit_index = 0
        big_index = 0
        zero_flag = False
        while num > 0:
            part = num % 10000
            if part != 0:
                part_str = ""
                digit_index = 0
                while part > 0:
                    digit = part % 10
                    if digit != 0:
                        part_str = digits_cn[digit] + units_cn[digit_index] + part_str
                    else:
                        if not zero_flag and part_str != "":
                            part_str = "零" + part_str
                        zero_flag = True
                    part //= 10
                    digit_index += 1
                result = part_str + big_units_cn[big_index] + result
            else:
                if not result.startswith("零"):
                    result = "零" + result
            num //= 10000
            big_index += 1
        return result.rstrip("零")

    integer_words = integer_to_cn(integer_part) + "元"

    if decimal_part == 0:
        return integer_words + "整"
    else:
        jiao = decimal_part // 10
        fen = decimal_part % 10
        decimal_words = ""
        if jiao > 0:
            decimal_words += digits_cn[jiao] + "角"
        if fen > 0:
            decimal_words += digits_cn[fen] + "分"
        return integer_words + decimal_words


def money_to_words(amount: float):
    # 四舍五入保留 2 位小数
    amount = round(amount, 2)
    integer_part = int(amount)
    decimal_part = round((amount - integer_part) * 100)

    # 千分符格式化数字
    formatted_amount = f"{amount:,.2f}"

    # 英文大写
    integer_words_en = num2words(integer_part, lang='en').replace("-", " ").replace(",", "")
    if decimal_part > 0:
        english = f"{integer_words_en.capitalize()} and {decimal_part:02d}/100 Dollars"
    else:
        english = f"{integer_words_en.capitalize()} Dollars"

    # 泰文大写
    integer_words_th = num2words(integer_part, lang='th')
    if decimal_part > 0:
        decimal_words_th = num2words(decimal_part, lang='th')
        thai = f"{integer_words_th}บาท{decimal_words_th}สตางค์"
    else:
        thai = f"{integer_words_th}บาทถ้วน"

    # 中文大写
    chinese = number_to_chinese(amount)
    # 英, 泰, 中
    return english, thai, chinese


class AccountMove(models.Model):
    _inherit = 'account.move'

    state = fields.Selection(selection_add=[('open', 'Open'), ('posted',)], ondelete={'open': 'cascade'})
    s_instruction_num = fields.Char(string='CN No.')
    sb_checking_id = fields.Many2one('sb.account.checking', string='Account Checking')
    amount_total_cn = fields.Char(string='Total Amount (CN)', compute='_compute_amount_total_thb', store=True)
    amount_total_thb = fields.Char(string='Total Amount (THB)', compute='_compute_amount_total_thb', store=True)
    amount_total_en = fields.Char(string='Total Amount (EN)', compute='_compute_amount_total_thb', store=True)

    @api.depends('amount_total')
    def _compute_amount_total_thb(self):
        for record in self:
            en, th, cn = money_to_words(record.amount_total or 0)
            record.amount_total_cn = cn
            record.amount_total_thb = th
            record.amount_total_en = en

    # @api.depends('restrict_mode_hash_table', 'state')
    # def _compute_show_reset_to_draft_button(self):
    #     for move in self:
    #         move.show_reset_to_draft_button = (
    #                 not move.restrict_mode_hash_table \
    #                 and (move.state == 'cancel' or (move.state == 'proforma' and not move.need_cancel_request))
    #         )
    def return_draft_button(self):
        for rec in self:
            if rec.state not in ['posted', 'open']:
                raise UserError(_("You can only return to draft state from 'Open' state."))
            rec.state = 'draft'
        return True

    @api.depends('date', 'auto_post')
    def _compute_hide_post_button(self):
        for record in self:
            record.hide_post_button = record.state != 'open' \
                                      or record.auto_post != 'no' and record.date > fields.Date.context_today(record)

    def sb_button_confirm(self):
        for rec in self:
            if not rec.invoice_date:
                raise ValidationError(_("Invoice date is required to determine the post button visibility."))
            rec.state = 'open'

    @api.depends('name', 'journal_id')
    def _compute_made_sequence_hole(self):
        # 客户发票跳过判断逻辑，始终设为 False
        customer_invoices = self.filtered(lambda m: m.move_type == 'out_invoice')
        for move in customer_invoices:
            move.made_sequence_hole = False
        other_moves = self - customer_invoices
        if not other_moves:
            return

        self.env.cr.execute("""
               SELECT this.id
                 FROM account_move this
                 JOIN res_company company ON company.id = this.company_id
            LEFT JOIN account_move other ON this.journal_id = other.journal_id
                                        AND this.sequence_prefix = other.sequence_prefix
                                        AND this.sequence_number = other.sequence_number + 1
                WHERE other.id IS NULL
                  AND this.sequence_number != 1
                  AND this.name != '/'
                  AND this.id = ANY(%(move_ids)s)
           """, {
            'move_ids': other_moves.ids,
        })
        made_sequence_hole = set(r[0] for r in self.env.cr.fetchall())
        for move in other_moves:
            move.made_sequence_hole = move.id in made_sequence_hole


class AccountMoveLine(models.Model):
    _inherit = 'account.move.line'

    s_first_sale_line_id = fields.Many2one('sale.order.line', string='First Sale Order Line',
                                           compute='_compute_first_sale_line', store=True)
    s_pn = fields.Char(string='PN', related='s_first_sale_line_id.pn')
    s_po = fields.Char(string='PO', related='s_first_sale_line_id.po')
    s_pn_rev = fields.Char(string='PN Rev.', related='s_first_sale_line_id.pn_rev')
    s_end_pn = fields.Char(string='End PN', related='s_first_sale_line_id.order_id.end_pn')
    s_end_po = fields.Char(string='End PO', related='s_first_sale_line_id.order_id.end_po')
    s_end_pn_rev = fields.Char(string='End PN Rev.', related='s_first_sale_line_id.order_id.end_pn_rev')

    @api.depends('sale_line_ids')
    def _compute_first_sale_line(self):
        for rec in self:
            if rec.sale_line_ids:
                rec.s_first_sale_line_id = rec.sale_line_ids[0]
            else:
                rec.s_first_sale_line_id = False


class SBAccountChecking(models.Model):
    _name = 'sb.account.checking'
    _description = 'Account Checking'
    _inherit = ['portal.mixin', 'mail.thread.main.attachment', 'mail.activity.mixin']

    name = fields.Char(string='Name', default='New')
    ship_instructions_lines = fields.Many2many('ship.instructions.line', string='Ship Instructions Lines', copy=False)
    ship_instructions = fields.Many2many('ship.instructions', string='Shipping Instructions',
                                         compute='_compute_ship_instructions')
    currency_id = fields.Many2one('res.currency', string='Currency', compute='_compute_currency', readonly=True)
    payment_id = fields.Many2one('account.payment.term', string='Payment', compute='_compute_payment_id')
    invoice_ids = fields.Many2many('account.move', string='Invoices', compute='_compute_invoice_ids')
    misc_ids = fields.Many2many(
        'account.move',
        string='Miscellaneous Invoices',
        relation='sb_account_checking_misc_invoice_rel',
        column1='sb_checking_id',  # 当前模型的ID列名
        column2='invoice_id',  # 目标模型的ID列名
        domain=[('move_type', '=', 'out_invoice')],  # 确保只选择发票
        copy=False
    )
    am_line_ids = fields.Many2many('account.move.line', string='Reconciled Lines', compute='_compute_rec_line_ids')
    account_payment_ids = fields.Many2many('account.payment', string='Payments', compute='_compute_payment_ids')
    received_amount = fields.Monetary(string='Received Amount', currency_field='currency_id', readonly=True)
    customer = fields.Many2one('res.partner', string='Customer', compute='_compute_customer', readonly=True)
    note = fields.Text(string='Note')
    finish_date = fields.Date(string='Finish Date')
    state = fields.Selection([
        ('cancel', 'Cancel'),
        ('draft', 'Readying'),
        ('confirm', 'Confirm'),
        ('account_checking', 'checking'),
        ('write_off', 'In Write-off.'),
        ('done', 'Done'),
    ], default='draft', tracking=True, string='State')
    expected_untaxed_amount = fields.Monetary(
        string='Expected Untaxed',
        currency_field='currency_id',
        compute='_compute_expected_amounts',
        store=True
    )
    expected_total_amount = fields.Monetary(
        string='Expected Total (Incl. Tax)',
        currency_field='currency_id',
        compute='_compute_expected_amounts',
        store=True
    )
    company_currency_id = fields.Many2one(
        'res.currency',
        string='Company Currency',
        compute='_compute_company_currency',
    )

    expected_income = fields.Monetary(
        string='Expected Income', currency_field='currency_id',
        compute='_compute_expected_amounts', store=True)
    credit_total_amount = fields.Monetary(
        string='Credit&Other', currency_field='currency_id',
        compute='_compute_expected_amounts', store=True)

    credit_note_ids = fields.Many2many(
        'account.move',
        string='Credit Notes',
        domain=[('move_type', '=', 'out_refund')],
        copy=False,
    )
    # all_account_move_ids = fields.Many2many('account.move', string='All Account Moves', compute='_compute_all_invoice_ids')
    association_inv_ids = fields.One2many('account.move', 'sb_checking_id', string='Invoices')
    outstanding_credits = fields.Monetary(
        string='Outstanding Credits',
        currency_field='currency_id',
        compute='_compute_outstanding_credits'
    )
    all_invoices_reconciled = fields.Boolean(
        string='All Invoices Reconciled',
        compute='_compute_all_invoices_reconciled',
        store=True,
        help="Indicates if all associated invoices are fully reconciled."
    )
    have_credit_note = fields.Boolean(compute='_compute_have_credit_note', string='Have Credit Note')
    year_month_eng = fields.Char(compute='_compute_year_month_eng', string='Month', store=True, readonly=False)

    refund_count = fields.Integer(compute='_compute_refund_count')

    payment_collection_date = fields.Date('Payment collection date', compute='_compute_payment_collection_date', store=True)

    def _compute_refund_count(self):
        for rec in self:
            rec.refund_count = self.env['account.move'].search_count([
                ('partner_id', '=', rec.customer.id),
                ('currency_id', '=', rec.currency_id.id),
                ('sb_checking_id', '=', False),
                ('move_type', '=', 'out_refund')
            ])

    @api.depends('association_inv_ids.invoice_date')
    def _compute_payment_collection_date(self):
        for rec in self:
            account_move_ids = rec.association_inv_ids.filtered(lambda x: x.invoice_date)
            date = max(account_move_ids.mapped('invoice_date'))
            payment_line_ids = rec.payment_id.line_ids
            if payment_line_ids:
                nb_days = payment_line_ids[0].nb_days
            else:
                nb_days = 0
            rec.payment_collection_date = date + relativedelta(months=1, day=1, days=-1) + timedelta(days=nb_days)

    # def _search_invoice_ids(self, operator, value):
    #     """
    #     支持标准用法：
    #       [('invoice_ids', 'in', [1,2,3])]
    #       [('invoice_ids', '=', False)]
    #     """
    #     # 先根据 value 拿到对应的 invoice 记录
    #     if operator == 'in':
    #         invoices = self.env['account.move'].browse(value)
    #     elif operator == '=' and not value:
    #         # 想找「没有发票」的 checking
    #         self._cr.execute("""
    #               SELECT DISTINCT checking_id
    #               FROM sb_account_checking_ship_instructions_lines
    #               WHERE name IS NOT NULL
    #           """)
    #         has_lines_ids = [r[0] for r in self._cr.fetchall()]
    #         return [('id', 'not in', has_lines_ids)]
    #     else:
    #         # 其余情况让框架二次过滤
    #         return []
    #
    #     # 用 invoice.name 反查 ship_instructions_lines.name
    #     names = invoices.mapped('name')
    #     if not names:
    #         return [('id', '=', False)]  # 无结果
    #
    #     # 查出含有这些 name 的 checking id
    #     self._cr.execute("""
    #           SELECT DISTINCT checking_id
    #           FROM sb_account_checking_ship_instructions_lines
    #           WHERE name IN %s
    #       """, (tuple(names),))
    #     checking_ids = [r[0] for r in self._cr.fetchall()]
    #
    #     return [('id', 'in', checking_ids)]

    @api.depends('ship_instructions_lines.out_date')
    def _compute_year_month_eng(self):
        for rec in self:
            if rec.ship_instructions_lines and rec.ship_instructions_lines[0].out_date:
                date = rec.ship_instructions_lines[0].out_date
                rec.year_month_eng = format_date(date, 'MMM yyyy', locale='en_US')
            else:
                rec.year_month_eng = ''

    def _compute_have_credit_note(self):
        for rec in self:
            rec.have_credit_note = False
            if rec.state not in ['draft'] or not rec.customer:
                return
            # 1. Search for all customer credit notes that are not yet associated with this record
            customer_credit_notes = self.env['account.move'].search([
                ('partner_id', 'child_of', rec.customer.id),
                ('move_type', '=', 'out_refund'),
                ('state', '=', 'posted'),  # Only consider posted credit notes
                ('payment_state', 'in', ('not_paid', 'in_payment')),  # Not fully paid/reconciled
                ('sb_checking_id', '=', False),  # Not yet associated with any checking record
            ])
            # Get IDs of credit notes already linked to this record
            existing_credit_note_ids = rec.credit_note_ids.ids
            # 2. Iterate and filter for unreconciled credit notes not in credit_note_ids
            for cn in customer_credit_notes:
                # Exclude credit notes already linked
                if cn.id in existing_credit_note_ids:
                    continue
                # Check if any receivable account move line is NOT reconciled
                is_unrereconciled_line_found = False
                for line in cn.line_ids:
                    # if line.account_id.internal_group == 'receivable' and not line.reconciled:
                    is_unrereconciled_line_found = True
                    break
                # If an unreconciled receivable line is found for this credit note
                if is_unrereconciled_line_found:
                    rec.have_credit_note = True
                    break  # Found a matching credit note for this record, no need to check further

    def action_refund_request(self):
        self.ensure_one()
        return {
            'name': _('Choice Sale Refund'),
            'view_mode': 'form',
            'res_model': 's.choice.sale.refund.wizard',
            'type': 'ir.actions.act_window',
            'context': {
                'default_sb_checking_id': self.id
            },
            'target': 'new',
        }

    def action_cancel(self):
        for rec in self:
            if rec.state not in ['confirm', 'account_checking']:
                raise UserError(_("You can only cancel account checking records in 'Confirm' 'Checking' state."))
            rec.state = 'cancel'
            rec.ship_instructions_lines.sudo().write({'sb_check_state': 'draft'})
            rec.finish_date = False
        return True

    def unlink(self):
        for rec in self:
            if rec.state not in ['draft']:
                raise UserError(
                    _("You can only delete account checking records not in 'Draft' state. Please use cancel."))
            for x in rec.ship_instructions_lines:
                x.sb_check_state = 'draft'
        return super().unlink()

    @api.depends('invoice_ids.state', 'invoice_ids.line_ids.reconciled')
    def _compute_all_invoices_reconciled(self):
        """
        计算所有关联发票是否都已完全核销。
        检查每张发票的应收账款行是否都已核销。
        """
        for rec in self:
            all_reconciled = True
            if not rec.invoice_ids:
                all_reconciled = False
            else:
                for invoice in rec.invoice_ids:
                    # 仅检查已过账的发票 (posted state)
                    if invoice.state != 'posted':
                        all_reconciled = False
                        break

                    receivable_lines = invoice.line_ids.filtered(
                        lambda l: l.account_id.account_type == 'asset_receivable' and l.debit > 0
                    )

                    # 检查所有应收账款行是否都已核销
                    if not all(line.reconciled for line in receivable_lines):
                        all_reconciled = False
                        break

            rec.all_invoices_reconciled = all_reconciled
            if rec.state == 'write_off' and rec.all_invoices_reconciled:
                rec.state = 'done'
                rec.finish_date = fields.Date.context_today(rec)
                for x in rec.ship_instructions_lines:
                    x.sb_check_state = 'done'

    def _compute_advance_payment_amount(self):
        for rec in self:
            payments = self.env['account.payment'].search([
                ('partner_id', '=', rec.customer.id),
                ('state', '=', 'posted'),
                ('payment_type', '=', 'inbound'),
                ('move_id.line_ids.full_reconcile_id', '=', False),
                ('amount', '>', 0),
            ])
            total = 0.0
            for p in payments:
                payment_currency = p.currency_id or p.journal_id.currency_id or rec.currency_id
                target_currency = rec.currency_id
                date = p.date or fields.Date.today()
                converted = payment_currency._convert(p.amount, target_currency, p.company_id, date)
                total += converted
            rec.advance_payment_amount = total

    accounting_user_emails = fields.Char(compute='_compute_accounting_user_emails', store=False)
    accounting_user_ids = fields.Many2many('res.users', compute='_compute_accounting_user_ids', store=False)

    def update_credit_note_ids(self):
        """
        This method finds credit notes related to the sale order lines of the current
        record's ship instructions, filtering for unreconciled notes that are
        not yet associated with an 'sb_checking_id', and then links them.
        """
        for rec in self:
            unique_sale_order_line_ids = set()
            for line in rec.ship_instructions_lines:
                if line.sale_order_line_id:
                    unique_sale_order_line_ids.add(line.sale_order_line_id.id)
            unique_sale_order_line_ids_list = list(unique_sale_order_line_ids)

            # Retrieve associated invoice lines from the unique sale order lines
            unique_invoice_line_ids = set()
            for sol_id in unique_sale_order_line_ids_list:
                sale_order_line = self.env['sale.order.line'].browse(sol_id)
                # Ensure sale_order_line exists to prevent errors
                if sale_order_line:
                    for invoice_line in sale_order_line.invoice_lines:
                        unique_invoice_line_ids.add(invoice_line.id)

            # Retrieve associated account moves from the unique invoice lines,
            # filtering specifically for 'out_refund' (credit notes) that meet criteria
            found_credit_note_ids = set()
            for invoice_line_id in unique_invoice_line_ids:
                invoice_line = self.env['account.move.line'].browse(invoice_line_id)
                # Ensure invoice_line and its corresponding move_id exist
                if not invoice_line or not invoice_line.move_id:
                    continue

                move = invoice_line.move_id
                # Check if it's a credit note ('out_refund') and if it's not yet
                # associated with an 'sb_checking_id' (i.e., not yet processed by a checking record)
                if move.move_type == 'out_refund' and not move.sb_checking_id:
                    # Further check payment and reconciliation status to select only truly unreconciled credit notes
                    # 'not_paid' and 'in_payment' payment states suggest it's not fully reconciled.
                    if move.payment_state in ('not_paid', 'in_payment'):

                        # A credit note is considered 'unreconciled' if at least one of its
                        # reconcilable lines is NOT reconciled.
                        is_fully_reconciled_by_lines = True
                        for line in move.line_ids:
                            # Check if the account line is marked as reconcilable and is not reconciled.
                            # We remove the internal_group check as per your suggestion.
                            if line.account_id.reconcile and not line.reconciled:
                                is_fully_reconciled_by_lines = False  # Found an unreconciled line
                                break  # No need to check further, it's not fully reconciled

                        # Add the credit note if it's not fully reconciled by its lines
                        # and its payment_state is not 'paid'
                        if not is_fully_reconciled_by_lines:
                            found_credit_note_ids.add(move.id)

            # Update the Many2many field with the found credit notes
            rec.credit_note_ids = [(6, 0, list(found_credit_note_ids))]

            # Provide feedback to the user based on whether credit notes were found
            if not rec.credit_note_ids:
                return {
                    'type': 'ir.actions.client',
                    'tag': 'display_notification',
                    'params': {
                        'title': _('No record'),
                        'type': 'warning',
                        'message': _(
                            'Can not find unreconciled credit notes that are not yet associated with a checking record.'),
                        'sticky': False,
                        'next': {'type': 'ir.actions.act_window_close'},
                    }
                }
            else:
                # First, assign the current record's ID to the 'sb_checking_id' field
                # on all found credit notes.
                for credit_note in rec.credit_note_ids:
                    credit_note.sb_checking_id = rec.id

                # Then, retrieve the names of the credit notes for the success message.
                credit_note_names = ', '.join(rec.credit_note_ids.mapped('name'))
                return {
                    'type': 'ir.actions.client',
                    'tag': 'display_notification',
                    'params': {
                        'title': _('Success'),
                        'type': 'success',
                        'message': _('Credit notes updated successfully.\nFound: %s') % credit_note_names,
                        'sticky': False,
                        'next': {'type': 'ir.actions.act_window_close'},
                    }
                }

    def _compute_accounting_user_emails(self):
        for rec in self:
            warehouse_dept = self.env['hr.department'].search([('code', '=', 'FIN')], limit=1)
            employees = self.env['hr.employee'].search([('department_id', '=', warehouse_dept.id)])
            emails = employees.mapped('user_id.partner_id.email')
            rec.accounting_user_emails = ','.join(filter(None, emails))

    def _compute_accounting_user_ids(self):
        for rec in self:
            warehouse_dept = self.env['hr.department'].search([('code', '=', 'FIN')], limit=1)
            employees = self.env['hr.employee'].search([('department_id', '=', warehouse_dept.id)])
            rec.accounting_user_ids = employees.mapped('user_id').filtered(lambda u: u.active)

    # def action_open_checking_preview(self):
    #     self.ensure_one()
    #     return {
    #         'type': 'ir.actions.act_url',
    #         'name': '預覽對帳單',
    #         'url': f'/account_checking/{self.id}',
    #         'target': 'new',
    #     }

    def action_apply_outstanding_credits(self):
        for rec in self:
            payments = self.env['account.payment'].search([
                ('partner_id', '=', rec.customer.id),
                ('state', '=', 'posted'),
                ('reconciled_invoice_ids', '=', False),
                ('payment_type', '=', 'inbound'),
                ('amount_residual', '>', 0),
            ])
            for payment in payments:
                payment.write({'reconciled_invoice_ids': [(4, inv.id) for inv in rec.invoice_ids]})

    @api.depends('customer')
    def _compute_outstanding_credits(self):
        for rec in self:
            payments = self.env['account.payment'].search([
                ('partner_id', '=', rec.customer.id),
                ('state', '=', 'posted'),
                ('payment_type', '=', 'inbound'),
                ('amount', '>', 0),
            ])
            rec.outstanding_credits = sum(payments.mapped('amount'))

    def action_open_credit_note_wizard(self):
        self.ensure_one()
        return {
            'type': 'ir.actions.act_window',
            'name': _('Create Credit Note'),
            'res_model': 'sb.credit.note.wizard',
            'view_mode': 'form',
            'target': 'new',
            'context': {
                'default_sb_checking_id': self.id,
                'default_partner_id': self.customer.id,
                'default_currency_id': self.currency_id.id,
                'default_journal_id': self.invoice_ids[:1].journal_id.id if self.invoice_ids else False,
            },
        }

    def action_adjust_invoice_quantity(self):
        for record in self:
            for return_order in record.return_ids:
                for return_line in return_order.product_return_moves:
                    move_lines = record.invoice_ids.mapped('invoice_line_ids').filtered(
                        lambda l: l.product_id == return_line.product_id
                    )
                    for line in move_lines:
                        line.quantity -= return_line.quantity

    def _compute_company_currency(self):
        for rec in self:
            rec.company_currency_id = self.env.company.currency_id.id

    @api.depends('invoice_ids.amount_total', 'credit_note_ids.amount_total', 'misc_ids.amount_total')
    def _compute_expected_amounts(self):
        for rec in self:
            total = sum(inv.amount_total for inv in rec.invoice_ids)
            ttl_misc = sum(inv.amount_total for inv in rec.misc_ids)
            total += ttl_misc
            rec.expected_total_amount = total
            credit_total = sum(inv.amount_total for inv in rec.credit_note_ids)
            rec.credit_total_amount = credit_total
            rec.expected_income = total - credit_total

    @api.depends('invoice_ids')
    def _compute_payment_ids(self):
        for rec in self:
            rec.account_payment_ids = self.env['account.payment'].search(
                [('reconciled_invoice_ids', 'in', rec.invoice_ids.ids)])

    def open_reconcile_view(self):
        # 获取所有相关的 line_ids 的 id
        line_ids = self.account_payment_ids.mapped('move_id.line_ids').ids
        # 调用 open_reconcile_view 方法
        return self.env['account.move.line'].browse(line_ids).open_reconcile_view()

    @api.depends('ship_instructions_lines')
    def _compute_ship_instructions(self):
        for rec in self:
            rec.ship_instructions = rec.ship_instructions_lines.mapped('ship_instructions_id')

    @api.depends('ship_instructions')
    def _compute_customer(self):
        for rec in self:
            if rec.ship_instructions:
                rec.customer = rec.ship_instructions[0].partner_id.id
            else:
                rec.customer = False

    @api.depends('ship_instructions')
    def _compute_currency(self):
        for rec in self:
            if rec.ship_instructions:
                rec.currency_id = rec.ship_instructions[0].currency_id.id
            else:
                rec.currency_id = self.env.company.currency_id.id

    @api.depends('ship_instructions')
    def _compute_payment_id(self):
        for rec in self:
            if rec.ship_instructions:
                rec.payment_id = rec.ship_instructions[0].payment_term.id
            else:
                rec.payment_id = False

    @api.depends('invoice_ids')
    def _compute_rec_line_ids(self):
        for rec in self:
            rec.am_line_ids = rec.invoice_ids.mapped('invoice_line_ids')

    @api.depends('ship_instructions_lines')
    def _compute_invoice_ids(self):
        for rec in self:
            unique_names = set()
            for line in rec.ship_instructions_lines:
                unique_names.add(line.name)
            invoice_records = self.env['account.move'].search([('name', 'in', list(unique_names))])
            rec.invoice_ids += invoice_records

    def _partial_reconcile_with(self, line1, line2, amount):
        """
        Performs reconciliation between two account.move.line records.
        Odoo's reconcile() method handles partial reconciliation automatically.

        :param line1: The first account.move.line record (e.g., invoice line).
        :param line2: The second account.move.line record (e.g., credit note line).
        :param amount: The amount to reconcile (not directly used by .reconcile() but for context).
        """
        if line1.account_id == line2.account_id and line1.company_id == line2.company_id:
            (line1 | line2).reconcile()
            line1.invalidate_recordset(['amount_residual', 'reconciled'])
            line2.invalidate_recordset(['amount_residual', 'reconciled'])
            self.message_post(body=f"Reconciled %s between line %s and %s" % (amount, line1.name, line2.name))
        else:
            raise UserError(_("Cannot reconcile lines with different accounts or companies."))

    def export_account_check_excel(self):
        self.ensure_one()
        report_name = 'billing_note'  # 報告名稱 Report name.
        report_action_obj = self.env['ir.actions.report']._get_report_from_name(report_name)
        return report_action_obj.report_action(self.id)
        # return {
        #     'type': 'ir.actions.act_url',
        #     'url': f'/export_shipping_instructions/%s?source=account_check' % self.id,
        #     'target': 'self',
        # }

    def action_account_confirm(self):
        """
        Confirms selected invoices, credit notes, and miscellaneous moves,
        then attempts to reconcile credit notes against invoices,
        prioritizing matching based on Sale Order Lines (SOL).
        """
        # self = self.sudo()
        self.env.su = True
        for rec in self:
            invoices = rec.invoice_ids
            credit_notes = rec.credit_note_ids
            misc_invoices = rec.misc_ids  # Assuming this refers to account.move records

            # 1. Post all draft invoices and credit notes
            # Filter only draft moves from the combined set of selected documents
            draft_moves = (invoices | credit_notes | misc_invoices).filtered(lambda m: m.state in ['draft', 'posted'])

            # Determine the sequence code to use based on currency or default
            sequence_code_to_use = self.env.ref('sb_sale.seq_sb_pack_delivery', raise_if_not_found=False)
            if rec.currency_id and rec.currency_id == self.env.ref('base.THB', raise_if_not_found=False):
                sequence_code_to_use = self.env.ref('sb_sale.seq_thai_inv_delivery', raise_if_not_found=False)

            # Assign names if they are not set or are default '/' and then post the moves
            for move in draft_moves:
                if sequence_code_to_use and (not move.name or move.name == '/'):
                    move.name = sequence_code_to_use.next_by_id()
                # Post the move to confirm it in the accounting system
                if move.state in ['draft', 'open']:
                    move.sudo().action_post()
                self.message_post(body=f"Posted document: %s (%s)" % (move.name, move.move_type))

            # 2. Collect all eligible receivable account lines from posted invoices
            # Filter for lines that are reconcilable, have a debit balance (receivable),
            # and are not yet fully reconciled.
            # invoice_lines_to_reconcile = invoices.mapped('invoice_line_ids')
            # Sort invoice lines by date to prioritize older invoices for reconciliation (FIFO)
            # invoice_lines_to_reconcile = invoice_lines_to_reconcile.sorted(key=lambda l: l.date)

            # 3. Reconcile Credit Notes against Invoices, prioritizing by Sale Order Line (SOL)
            if credit_notes:
                for note in credit_notes:
                    for cr_invoice_line in note.invoice_line_ids:
                        for invoice in invoices:
                            for inv_invoice_line in invoice.invoice_line_ids:
                                if not (inv_invoice_line.sale_line_ids & cr_invoice_line.sale_line_ids):
                                    continue
                                inv_rec_line = invoice.line_ids.filtered(
                                    lambda l: l.account_id.reconcile and l.debit > 0 and not l.reconciled
                                )
                                cr_rec_line = note.line_ids.filtered(
                                    lambda l: l.account_id.reconcile and l.credit > 0 and not l.reconciled
                                )
                                for dr_line in inv_rec_line:
                                    for cr_line in cr_rec_line:
                                        if dr_line.account_id != cr_line.account_id:
                                            continue
                                        if cr_line.amount_residual == 0:
                                            continue  # ✅ 已核銷完，就不處理

                                        amount = min(abs(dr_line.amount_residual), abs(cr_line.amount_residual))
                                        if amount <= 0:
                                            continue

                                        self._partial_reconcile_with(dr_line, cr_line, amount)

                                        self.message_post(body=_(
                                            f"已使用扣款 %s，核銷發票 %s，金額：%s" % (
                                                cr_line.name, dr_line.move_id.name, amount)
                                        ))

                                        dr_line.invalidate_recordset(['amount_residual', 'reconciled'])
                                        cr_line.invalidate_recordset(['amount_residual', 'reconciled'])

                                        # ✅ 若該 cr_line 已用盡金額，就不處理更多發票
                                        if cr_line.amount_residual == 0:
                                            break
                                    if all(l.amount_residual == 0 for l in
                                           note.line_ids.filtered(lambda l: l.credit > 0)):
                                        break  # ✅ 整張退款單核銷完畢，結束 note 處理
                            if all(l.amount_residual == 0 for l in note.line_ids.filtered(lambda l: l.credit > 0)):
                                break
                        if all(l.amount_residual == 0 for l in note.line_ids.filtered(lambda l: l.credit > 0)):
                            break

            # After all posting and reconciliation attempts, update the record's state
            rec.state = 'write_off'
            self.message_post(body=_("Account confirmation and reconciliation process completed."))

    def action_confirm(self):
        for rec in self:
            rec.state = 'account_checking'
            if rec.name == 'New':
                seq = self.env['ir.sequence'].next_by_code('sb.account.checking')
                rec.name = rec.customer.code + seq
            # # 生成 Excel 文件
            # file_name, file_content = rec.export_account_check_excel()
            # #
            # # # 创建附件
            # attachment = self.env['ir.attachment'].create({
            #     'name': file_name,
            #     'type': 'binary',
            #     'datas': base64.b64encode(file_content).decode(),
            #     'res_model': self._name,
            #     'res_id': rec.id,
            #     'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            # })

            # 发送邮件
            # template = self.env.ref('sb_sale.email_template_checking_confirm')
            # if not template:
            #     raise UserError("找不到邮件模板，请确认已正确加载。")
            # template.with_context(attachment_ids=[]).send_mail(rec.id, force_send=True)

    def action_open_reconcile_wizard(self):
        self.ensure_one()
        return {
            'type': 'ir.actions.act_window',
            'name': _('Reconcile Invoices'),
            'res_model': 'sb.account.checking.wizard',
            'view_mode': 'form',
            'target': 'new',
            'context': {
                'default_checking_id': self.id,
            },
        }

    def action_open_create_misc_invoice_wizard(self):
        self.ensure_one()

        wizard = self.env['sb.misc.invoice.wizard'].create({
            'account_checking_id': self.id,
        })

        return {
            'type': 'ir.actions.act_window',
            'name': 'Create Miscellaneous Invoice',
            'res_model': 'sb.misc.invoice.wizard',
            'view_mode': 'form',
            'res_id': wizard.id,
            'target': 'new',
            'context': self.env.context,
        }

    def return_market(self):
        for rec in self:
            rec.state = 'draft'
        return {
            'type': 'ir.actions.client',
            'tag': 'reload',
        }
